package searcher;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

//索引
public class Index {
    private static final String INDEX_PATH = "E:\\javaEE\\doc_searcher_index\\";

    private ObjectMapper objectMapper = new ObjectMapper();

    //使用数组下标表示 docId
    private ArrayList<DocInfo> forwardIndex = new ArrayList<>();

    //锁对象
    private Object locker1 = new Object();
    private Object locker2 = new Object();

    //使用哈希表表示倒排索引
    //key 词
    //value 一组和这个词有关联的文章
    private HashMap<String, ArrayList<Weight>> invertedIndex = new HashMap<>();

    /**
     * 给定一个docId，在正排索引中，查询文档的详细信息
     * @param docId 文档ID
     * @return 文档信息类
     */
    public DocInfo getDocINfo(int docId){
        return forwardIndex.get(docId);
    }

    /**
     * 给定一个词，在倒排索引中，查哪些文档和这个词关联
     * @param term 搜索词
     * @return 文档ID集合
     */
    public List<Weight> getInverted(String term){
        return invertedIndex.get(term);
    }

    /**
     * 往索引中新增一个文档
     * @param title 标题
     * @param url 链接
     * @param content 正文
     */
    public void addDoc(String title, String url, String content){
        //同时给正排索引和倒排索引新增信息
        //正排索引
        DocInfo docInfo = buildForward(title, url, content);
        //倒排索引
        buildInverted(docInfo);
    }

    private void buildInverted(DocInfo docInfo) {
        class WordCnt{
            //这个词在标题中出现的次数
            public int titleCount;
            //这个词在正文中出现的次数
            public int contentCount;
        }

        //统计词频
        HashMap<String, WordCnt> wordCntHashMap = new HashMap<>();
        //对标题进行分词
        List<Term> terms = ToAnalysis.parse(docInfo.getTitle()).getTerms();
        //统计每个词出现的次数
        for(Term term : terms){
            String word = term.getName();
            WordCnt wordCnt = wordCntHashMap.get(word);
            if(wordCnt == null){
                WordCnt newWordCnt = new WordCnt();
                newWordCnt.titleCount = 1;
                newWordCnt.contentCount = 0;
                wordCntHashMap.put(word, newWordCnt);
            }else {
                wordCnt.titleCount += 1;
            }
        }
        //对正文进行分词
        terms = ToAnalysis.parse(docInfo.getContent()).getTerms();
        for (Term term : terms){
            String word = term.getName();
            WordCnt wordCnt = wordCntHashMap.get(word);
            if(wordCnt == null){
                WordCnt newWordCnt = new WordCnt();
                newWordCnt.titleCount = 0;
                newWordCnt.contentCount = 1;
                wordCntHashMap.put(word, newWordCnt);
            }else {
                wordCnt.contentCount += 1;
            }
        }
        //统计每个词出现的次数
        //结果汇总——>HashMap
        //权重 = 标题中出现的次数 * 10 + 正文中出现的次数
        //遍历这个HashMap，依次更新倒排索引中的结构
        for(Map.Entry<String, WordCnt> entry : wordCntHashMap.entrySet()){
            synchronized (locker2){
                List<Weight> invertedList =  invertedIndex.get(entry.getKey());
                if(invertedList == null){
                    ArrayList<Weight> newInvertedList = new ArrayList<>();
                    Weight weight = new Weight();
                    weight.setDocId(docInfo.getDocId());
                    weight.setWeight(entry.getValue().titleCount * 10 + entry.getValue().contentCount);
                    newInvertedList.add(weight);
                    invertedIndex.put(entry.getKey(),newInvertedList);
                }else{
                    Weight weight = new Weight();
                    weight.setDocId(docInfo.getDocId());
                    weight.setWeight(entry.getValue().titleCount * 10 + entry.getValue().contentCount);
                    invertedList.add(weight);
                }
            }
        }
    }

    private DocInfo buildForward(String title, String url, String content) {
        DocInfo docInfo = new DocInfo();
        docInfo.setTitle(title);
        docInfo.setUrl(url);
        docInfo.setContent(content);
        synchronized (locker1){
            docInfo.setDocId(forwardIndex.size());
            forwardIndex.add(docInfo);
        }

        return docInfo;
    }

    /**
     * 把内存中的索引结构保存到磁盘中
     */
    public void save(){
        long beg = System.currentTimeMillis();
        File indexPathFile = new File(INDEX_PATH);
        if(!indexPathFile.exists()){
            indexPathFile.mkdirs();
        }
        File forwardIndexFile = new File(INDEX_PATH + "forward.txt");
        File invertedIndexFile = new File(INDEX_PATH + "inverted.txt");
        try {
            objectMapper.writeValue(forwardIndexFile, forwardIndex);
            objectMapper.writeValue(invertedIndexFile, invertedIndex);
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("保存索引完成！消耗时间：" + (end - beg) + " ms");
    }

    /**
     * 把磁盘中的索引数据加载到内存中
     */
    public void load(){
        long beg = System.currentTimeMillis();
        System.out.println("加载索引开始！");
        File forwardIndexFile = new File(INDEX_PATH + "forward.txt");
        File invertedIndexFile = new File(INDEX_PATH + "inverted.txt");
        try {
            forwardIndex = objectMapper.readValue(forwardIndexFile, new TypeReference<ArrayList<DocInfo>>() {});
            invertedIndex = objectMapper.readValue(invertedIndexFile, new TypeReference<HashMap<String, ArrayList<Weight>>>() {});
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("加载索引结束！消耗时间：" + (end - beg) + " ms");
    }

    public static void main(String[] args) {
        Index index =new Index();
        index.load();
        System.out.println("索引加载完成");
    }

}
